package Busquedas;

import Estados.Estado;
import Juegos.Juego;
import java.util.ArrayList;

/**
 * Clase para implementar la busqueda heuristica de A*.
 */
public class AEstrella extends Busqueda{


    private int contadorNodos;
    private int maxContadorNodos;
    ArrayList<Estado> pilaAbiertos;
    ArrayList<Estado> expandidos;
    private long tiempoTotal;
    private boolean controlCiclos;
    protected ArrayList<Estado> cerrados;

    public AEstrella(Estado ini,Juego juego) {
            super(ini,juego);
            this.expandidos=new ArrayList<Estado>();
            this.pilaAbiertos=new ArrayList<Estado>();
            this.cerrados=new ArrayList<Estado>();
            pilaAbiertos.add(ini);
            tiempoTotal = 0;
    }

	public ArrayList<Estado> resolver() {
            long tiempo1=System.currentTimeMillis();
            boolean solucion = false;
            while (!solucion && this.pilaAbiertos.size()!=0){
                int mejor = indiceMejor();
                Estado e=this.pilaAbiertos.remove(mejor);
                if(juego.esFinal(e)){
                    solucionar(e);
                    solucion=true;
                }
                else{
                    Estado e2 = estaCerrado(e);
                    if (e.Iguales(e, e2)){

                    }
                    Estado e3 = estaAbierto(e);
                    if (e.Iguales(e, e3)){
                        int coste1 = e.getCoste();
                        int coste2 = e3.getCoste();
                        if (coste1 < coste2){
                            actualizaCerrados((coste2 - coste1), e3);
                            generaHijos(e);
                        }
                        else {
                            actualizaCerrados((coste1 - coste2), e);
                            generaHijos(e3);
                        }
                    }
                    if (!e.Iguales(e, e2) && !e.Iguales(e, e3)){
                        generaHijos(e);
                    }
                }
                long tiempo2=System.currentTimeMillis();
                tiempoTotal = tiempoTotal + (tiempo2 - tiempo1);
            }
            return this.solucion;
	}

    private boolean estaExpandido(Estado e){
        for(int i=0;i<this.expandidos.size();i++){
            if(e.Iguales(e, this.expandidos.get(i)))
                return true;
        }
        return false;


    }

    private void generaHijos(Estado e){
        this.expandidos.add(e);
        int ops=this.juego.getNumOperadores();
        for(int i=0; i<ops; i++){
            Estado nuevo=this.juego.operar(i,e);
            //control de ciclos y de estado peligro
            if( nuevo!=null && !estaExpandido(nuevo) && !juego.esPeligro(nuevo))
                this.pilaAbiertos.add(contadorNodos, nuevo);
                contadorNodos++;
        }
    }

        private ArrayList<Estado> generaHijosCerrados(Estado e){
        ArrayList<Estado> hijosCerrados = new ArrayList<Estado>();
        cerrados.add(e);
        int ops=this.juego.getNumOperadores();
        for(int i=0; i<ops; i++){
            Estado nuevo=this.juego.operar(i,e);
            //control de ciclos y de estado peligro
            if( nuevo!=null && !estaExpandido(nuevo))
                hijosCerrados.add(i,nuevo);
        }
        return hijosCerrados;
    }

	public int indiceMejor(){
		int indiceMejor = 0;
		int i = 0;
		int menorValor = (pilaAbiertos.get(0).getHeuristica() + pilaAbiertos.get(0).getCoste());
		while (i < pilaAbiertos.size()){
			if (menorValor > (pilaAbiertos.get(i).getHeuristica()+pilaAbiertos.get(i).getCoste())){
				indiceMejor = i;
				menorValor = (pilaAbiertos.get(i).getHeuristica()+pilaAbiertos.get(i).getCoste());
			}else{
				i++;
			}
		}
		return indiceMejor;
	}

	protected boolean estaEnAbiertos(Estado e){
		boolean encontrado=false;
		int i=0;
		while (!encontrado && (i<pilaAbiertos.size())){
			if (e.Iguales(e, pilaAbiertos.get(i))){
				encontrado=true;
			}
			else i++;
		}
		return encontrado;
	}

    	public Estado estaAbierto(Estado e){
            int i=0;
            while ((i<pilaAbiertos.size())){
                    if (e.Iguales(pilaAbiertos.get(i),e)){
                            return pilaAbiertos.get(i);
                    }
                    else i++;
            }
            return e;
	}
        public int indiceAbierto(Estado e){
		int i=0;
		while ((i<pilaAbiertos.size())){
                    if (e.Iguales(pilaAbiertos.get(i),e)){
                            return i;
                    }
			else i++;
		}
		return -1;
	}
   	/**
	 * Metodo que devuelve cierto si el nodo esta entre los nodos cerrados
	 */
    	protected boolean estaEnCerrados(Estado e){
            boolean encontrado=false;
            int i=0;
            while (!encontrado && (i<cerrados.size())){
                    if (e.Iguales(e, pilaAbiertos.get(i))){
                            encontrado=true;
                    }
                    else i++;
            }
            return encontrado;
	}
    	public Estado estaCerrado(Estado e){
            int i=0;
            while ((i<pilaAbiertos.size())){
                    if (e.Iguales(e, pilaAbiertos.get(i))){
                            return cerrados.get(i);
                    }
                    else i++;
            }
            return e;
	}
   public int indiceCerrado(Estado e){
            int i=0;
            while (i<pilaAbiertos.size()){
                    if (e.Iguales(e, cerrados.get(i))){
                            return i;
                    }
                    else i++;
            }
            return -1;
	}

    private void actualizaCerrados(int diferencia,Estado e) {
        ArrayList<Estado> hijosCerrados = new ArrayList<Estado>();
        hijosCerrados = generaHijosCerrados(e);
        for(int i=0; i < hijosCerrados.size(); i++){
            if(estaEnAbiertos(hijosCerrados.get(i))){
                int indice=indiceAbierto(hijosCerrados.get(i));
                pilaAbiertos.get(indice).setCoste(pilaAbiertos.get(indice).getCoste() - diferencia);
                pilaAbiertos.get(indice).setPadre(e.getPadre());
                actualizaCerrados(diferencia,pilaAbiertos.get(indice));
            }
            if(estaEnCerrados(hijosCerrados.get(i))){
                int indice=this.indiceCerrado(hijosCerrados.get(i));
                cerrados.get(indice).setCoste(cerrados.get(indice).getCoste() - diferencia);
                cerrados.get(indice).setPadre(e.getPadre());
                actualizaCerrados(diferencia,cerrados.get(indice));
            }
        }
    }
}